package hu.vissy.yacap.option;

import hu.vissy.yacap.exception.CommandLineParserException;

import java.util.regex.Pattern;

/**
 * Base class for option definitions.
 *
 *
 * @author balage
 *
 * @param <T>
 *            Type of the value the option represents.
 */
public abstract class CommandLineOption<T> {

    /**
     * Pattern to validate long form.
     */
    private static final Pattern LONG_FORM_PATTERN = Pattern.compile("^[a-z0-9][a-z0-9]*$");

    public static final String DEFAULT_VALUE_LABEL = "value";

//    /**
//     * Type of the option.
//     *
//     * @author balage
//     *
//     */
//    public enum OptionType {
//        /** 4-byte signed integer */
//        INT,
//        /** 8-byte signed integer */
//        LONG,
//        /** logical value */
//        BOOLEAN,
//        /** floating point value */
//        DOUBLE,
//        /** string value */
//        STRING,
//        /** enumeration */
//        ENUM
//    }

    /**
     * Special indicator for options with no short (one character) command line
     * form.
     */
    public static final char NO_SHORT_FORM = '\0';

    /**
     * Type of the command line.
     */
    private Class<?> dataType;

    /**
     * The short form of the option.
     *
     * <p>
     * The short form contains only one character and indicated with a single
     * minus sign in command line.
     * </p>
     */
    private char shortForm = NO_SHORT_FORM;

    /**
     * The long form of the option.
     *
     * <p>
     * The long form usually a descriptive expression and indicated as double
     * hyphen (--) in command line. The long form can contain only alphanumeric
     * characters and hyphen.
     * </p>
     */

    private String longForm;
    /**
     * The description of the option.
     *
     * <p>
     * This description is used to display command line help. The text will be
     * rewrapped by the display engine.
     * </p>
     */
    private String description = "";

    /**
     * Label of the value in command line help.
     */
    private String valueName = DEFAULT_VALUE_LABEL;

    /**
     * Value policy.
     */
    private ValuePolicy valuePolicy = ValuePolicy.REQUIRED;

    /**
     * Default value used when no value specified in command line.
     */
    private T defaultValue;

    /**
     * Default behavior of the option.
     */
    private OptionPolicy defaultOptionPolicy = OptionPolicy.OPTIONAL;

    /**
     * The key used as result value map key. By default, it is the same as
     * {@linkplain #longForm}.
     */
    private String valueKey = null;

    /**
     * Constructor.
     *
     * @param dataType
     *            The type of the option.
     * @param longForm
     *            The long form of the option.
     */
    CommandLineOption(Class<?> dataType, String longForm) {
        super();
        this.dataType = dataType;
        setLongForm(longForm);
    }

    /**
     * @param longForm
     *            the longForm to set
     */
    private void setLongForm(String longForm) {
        if (LONG_FORM_PATTERN.matcher(longForm).matches()) {
            this.longForm = longForm;
        } else {
            throw new CommandLineParserException("Long form should contain only alphanumeric characters and hyphen. The value '" + longForm
                    + "' fails this validation");
        }
    }

    /**
     * @return The type of the option.
     */
    public Class<?> getDataType() {
        return dataType;
    }

    /**
     * @return The short form of the option.
     */
    public char getShortForm() {
        return shortForm;
    }

    /**
     * @return The long form of the option.
     */
    public String getLongForm() {
        return longForm;
    }

    /**
     * @return The description of the option.
     */
    public String getDescription() {
        return description;
    }

    /**
     * @return The default value of the option.
     */
    public T getDefaultValue() {
        return defaultValue;
    }

    /**
     * @return the default option behavior policy.
     */
    public OptionPolicy getDefaultOptionPolicy() {
        return defaultOptionPolicy;
    }

    /**
     * @return The label of the value to be used in command line help.
     */
    public String getValueName() {
        return valueName;
    }

    /**
     * @return The value handle policy.
     */
    public ValuePolicy getValuePolicy() {
        return valuePolicy;
    }

    /**
     * Sets the short form of the option.
     *
     * @param shortForm
     *            the character used for short form.
     *
     * @see {@linkplain CommandLineOption#NO_SHORT_FORM}
     */
    void setShortForm(char shortForm) {
        this.shortForm = shortForm;
    }

    /**
     * Sets the description of the option.
     *
     * @param description
     *            The option description.
     */
    void setDescription(String description) {
        this.description = description;
    }

    /**
     * Sets the label of the value field in command line help.
     *
     * <p>
     * If not specified, the default value is {@value #DEFAULT_VALUE_LABEL}.
     * </p>
     *
     * @param valueName
     *            The symbolic name of the option.
     */
    void setValueName(String valueName) {
        this.valueName = valueName;
    }

    /**
     * Sets the default value of the option.
     *
     * @param defaultValue
     *            The default value.
     */
    void setDefaultValue(T defaultValue) {
        this.defaultValue = defaultValue;
    }

    /**
     * @return True if the option has short form.
     */
    public boolean hasShortForm() {
        return shortForm != NO_SHORT_FORM;
    }

    /**
     * @return True if the option required value.
     */
    @Deprecated
    public boolean hasValue() {
        return true;
    }

    /**
     * @return The key used for result value identification.
     */
    public String getValueKey() {
        if (valueKey == null) {
            return longForm;
        } else {
            return valueKey;
        }
    }

    /**
     * @param valueKey
     *            the valueKey to set
     */
    void setValueKey(String valueKey) {
        this.valueKey = valueKey;
    }

    /**
     * @param valuePolicy
     *            The policy of value handling.
     */
    void setValuePolicy(ValuePolicy valuePolicy) {
        this.valuePolicy = valuePolicy;
    }

    /**
     * @param defaultOptionPolicy
     *            the default option behavior policy
     */
    void setDefaultOptionPolicy(OptionPolicy defaultOptionPolicy) {
        this.defaultOptionPolicy = defaultOptionPolicy;
    }

// /**
// * Az opció szignatúráját szolgáltatja.
// * <p>
// * A szignatúra a {@linkplain #getForm(boolean)} eredménye szögletes
// * zárójellel körülvéve, ha nem kötelező.
// * </p>
// *
// * @return Az opció szignatúrája.
// */
// public String getSignature() {
// StringBuilder sb = new StringBuilder();
// if (!isRequired()) {
// sb.append('[');
// }
// sb.append(getForm(true));
// if (!isRequired()) {
// sb.append(']');
// }
// return sb.toString();
// }
//
// /**
// * Az opció formátumát szolgáltatja.
// * <p>
// * A formátum az opció rövid és hosszú formájából és a parméter
// nevéből áll
// * össze.
// * </p>
// *
// * @param encloseForms
// * Ha igaz és van rövid forma is megadva, a két formát
// * zárójelezi.
// * @return A formázott opció formátum.
// */
// public String getForm(boolean encloseForms) {
// StringBuilder sb = new StringBuilder();
// if (hasShortForm()) {
// if (encloseForms) {
// sb.append("{");
// }
// sb.append('-').append(getShortForm()).append(" ");
// }
// sb.append("--").append(getLongForm());
//
// if (hasShortForm() && encloseForms) {
// sb.append("}");
// }
// if (hasValue()) {
// sb.append(" <").append(getValueName()).append(">");
// }
// return sb.toString();
// }

    /**
     * Parses and converts the text to value.
     *
     * @param value
     *            the text to process
     * @return the processed value
     * @throws CommandLineParserException
     *             If the conversion or parsing fails.
     */
    public abstract T parseValue(String value) throws CommandLineParserException;

    @Override
    public String toString() {
        return "CommandLineOption [shortForm=" + shortForm + ", longForm=" + longForm + ", dataType=" + dataType + ", valueName=" + valueName + ", policy="
                + defaultOptionPolicy + "]";
    }

}
